﻿using System;
using System.Collections.Generic;
using Sern.Abstracts.Booster;
using Sern.Abstracts.Collector;
using Sern.Abstracts.Common;
using Sern.Abstracts.Filter;
using Sern.Abstracts.Splitter;
using Sern.Abstracts.Tracker.DataContracts;
using Sern.Abstracts.Tracker.ServiceContracts;
using Sern.Common;

namespace Sern.ServiceImplementation.MapReduce
{
    public sealed class MapReduce<TKey, TVal> : ISplitter<TKey, TVal>, IFilter<TKey,TVal>, IBooster<TKey,TVal>, ICollector<TKey,TVal>
        
    {
        private Action<TKey, TVal, Action<object,object>>  _mapReduceDelegate;
        private LogLevel _lowestLoggableTrace = LogLevel.None;
        private Action<object,object> _nextServiceInvokerDelegate;
        private Func<IClient<ITrackerTelemetryReceiver>> _trackerTelemetryReceiverFactory;
        private readonly Func<IClient<ITrackerLogging>> _loggerFactory;
        private Func<Guid, Guid, bool> _isRedundancyRequirementSatisifedDelegate;
        //private Action _end;


        public Guid JobId { get; private set; }
        public Guid ClientId { get; private set; }

        public ClientType Type { get { return ClientType.Splitter; } }


        /// <summary>
        /// A new singleton client handler.
        /// </summary>
        /// <param name="nextMrFactory">This is a factory because we dont' want the client caching this when the underlying connection might have been closed. Create new connection per call</param>
        /// <param name="trackerTelemetryReceiverFactory"> </param>
        /// <param name="loggerFactory"> </param>
        /// <param name="isRedundancyRequirementSatisifedDelegate"> </param>
        /// <param name="jobId"> </param>
        /// <param name="clientId"></param>
        /// <param name="nextServiceInvokerDelegate"> </param>
        /// <param name="mapReduceDelegate"></param>
        /// <param name="lowestLoggableTrace"> </param>
        public MapReduce(
            Func<IClient<ITrackerTelemetryReceiver>> trackerTelemetryReceiverFactory, 
            Func<IClient<ITrackerLogging>> loggerFactory,
            Func<Guid, Guid, bool> isRedundancyRequirementSatisifedDelegate,
            Guid jobId, 
            Guid clientId,
            Action<object, object> nextServiceInvokerDelegate, 
            Action<TKey, TVal, Action<object, object>> mapReduceDelegate,
            LogLevel lowestLoggableTrace = LogLevel.Trace
            )
        {
            //_end = end;
            _mapReduceDelegate = mapReduceDelegate;
            _isRedundancyRequirementSatisifedDelegate = isRedundancyRequirementSatisifedDelegate;
            _lowestLoggableTrace = lowestLoggableTrace;
            _nextServiceInvokerDelegate = nextServiceInvokerDelegate;
            _trackerTelemetryReceiverFactory = trackerTelemetryReceiverFactory;
            _loggerFactory = loggerFactory;
            JobId = jobId;
            ClientId = clientId;

        }

        private async void NotifyTracker(ProcessingNotice notice)
        {
            using (var tracker = _trackerTelemetryReceiverFactory())
            {
                tracker.Proxy.Notify(notice);
            }
        }

        void IMapReduce<TKey, TVal>.Handle(TKey key, TVal value)
        {
            NotifyTracker(
                    new ProcessingNotice
                    {
                        ClientId = ClientId,
                        JobId = JobId,
                        ProcessingStage = ProcessingStage.Processing,
                        Key = key.GetKeyHash(),
                    });

            _mapReduceDelegate.BeginInvoke(key,value,(nextKey,nextValue) =>
                                  {
                                      var redundancyKey = nextKey.GetRedundancyKeyHash();

                                      while (!_isRedundancyRequirementSatisifedDelegate(nextKey.GetKeyHash(), redundancyKey))
                                      {
                                          try
                                          {
                                              _nextServiceInvokerDelegate(nextKey, nextValue);

                                              //Notify the newly created key.
                                              NotifyTracker(
                                                  new ProcessingNotice
                                                      {
                                                          ClientId = ClientId,
                                                          JobId = JobId,
                                                          ProcessingStage = ProcessingStage.Initiated,
                                                          Key = nextKey.GetKeyHash(),
                                                          ParentKey = key.GetKeyHash(),
                                                          RedundancyKey = nextKey.GetRedundancyKeyHash()
                                                      });

                                              //Notify the current key processing is done.
                                              NotifyTracker(
                                                  new ProcessingNotice
                                                      {
                                                          ClientId = ClientId,
                                                          JobId = JobId,
                                                          ProcessingStage = ProcessingStage.Completed,
                                                          Key = key.GetKeyHash()
                                                    });

                                              LogEntry(new EmissionTrace
                                              {
                                                  Message = "Success",
                                                  OriginatingKey = key.GetKeyHash(),
                                                  Output = new KeyValuePair<object, object>(nextKey, nextValue),
                                                  Severity = LogLevel.Trace,
                                                  OriginatingRedundancyKey = key.GetRedundancyKeyHash()
                                              });
                                          }
                                          catch (Exception e)
                                          {
                                              LogEntry(new EmissionTrace
                                                  {
                                                      Message = e.Message,
                                                      OriginatingKey = key.GetKeyHash(),
                                                      Output = new KeyValuePair<object, object>(nextKey, nextValue),
                                                      Severity = LogLevel.Error,
                                                      OriginatingRedundancyKey = key.GetRedundancyKeyHash()
                                                  });
                                          }

                                      }
                                  }, ar =>
                                         {
                                             try
                                             {
                                                 ar.AsyncWaitHandle.WaitOne();
                                             }
                                             catch(Exception e)
                                             {
                                                 LogEntry(new EmissionTrace
                                                              {
                                                                  Message = e.Message,
                                                                  OriginatingKey = key.GetKeyHash(),
                                                                  Output = new KeyValuePair<object, object>(null,null),
                                                                  Severity = LogLevel.Error,
                                                                  OriginatingRedundancyKey = key.GetRedundancyKeyHash()
                                                              });
                                             }
                                         }, null);
        }

        private async void LogEntry(EmissionTrace emissionTrace)
        {
            using(var logger = _loggerFactory())
            {
                if (emissionTrace.Severity > _lowestLoggableTrace
                    && logger != null)
                    logger.Proxy.Log(emissionTrace);
            }
            //TODO: Customized stack dump logging is not handled yet. Figured out a way to do so. When a key failed to produce, the key that it's producing and the producing key may both need to be logged for completeness, but the produced key is likely MORE important as the relationship is already graphed in the emission tracing.
        }

        void ILogToggleable.SetLogLevel(Abstracts.Common.LogLevel logLevel = LogLevel.Trace)
        {
            _lowestLoggableTrace = logLevel;
        }
    }
}
